home *** CD-ROM | disk | FTP | other *** search
/ Nebula 2 / Nebula Two.iso / SourceCode / Palettes / PAScrollViewDeluxe / PAScrollViewDeluxe.m < prev    next >
Text File  |  1995-06-12  |  47KB  |  1,142 lines

  1. // Format: 80 columns, tabs = 4 spaces
  2. #import "PAScrollViewDeluxe.h"
  3. #import "Ruler.h"
  4.  
  5. /******************************************************************************    
  6.     PAScrollViewDeluxe
  7.     
  8.     This object is an enhanced subclass of ScrollView. It adds the following features:
  9.     
  10.     Support for a 'top view', a view at the top of the scroll view that scrolls horizontally with the document but remains static when the document is scrolled vertically. This is useful to add column headers, rulers, etc, to a document. The top view can be added from inside of IB by connecting the topView outlet.
  11.     
  12.     Support for a 'left view', a view to the left of the scroll view that scrolls vertically with the document, but remains static when the document is scrolled horizontally. This is useful for adding rulers, line numbers, etc, to a document. The left view can be added from inside of IB by connecting the leftView outlet.
  13.     
  14.     Automatic support for rulers. If a topView or leftView has not been set, a call to setShowTopView:YES, setShowLeftView:YES, showRulers: or toggleRulers: will instanciate a member of rulerClass (see setRulerClass:). Depending on which ruler is called, the scrollview will try to call setHorizontal or setVertical on the new rulerClass instance. The default class is Ruler (from Draw). A ruler that supports flipping and smooth zooming is forthcoming.
  15.     
  16.     Support for synchronizing other scroll views. Other ScrollViews can be made to scroll, size and zoom with respect to the docView by using the addSyncViews: methods. This is useful for ruler type views that need to exist outside of the scrollers. One synchronized view of each type can be added in IB by connecting a given ScrollView to the syncViews, horizSyncViews or vertSyncViews outlets. Be sure and connect to a ScrollView and not its content view (this can be tricky for ScrollViews without scrollers). Synchronized ScrollViews should not have scrollers of their own (but they can).
  17.  
  18.     Automatic support for page up/down & left/right buttons. Simply call the method setPageUpDownButtonsVisible:YES or setPageLeftRightButtonsVisible:YES and these are added automatically. They are removed temporarily if the scroller is too small(less than an inch), if there is nothing to be scrolled or if the scrolling area is less than 2 pages.
  19.     
  20.     Automatic support for zooming. Simply call the method setShowZoomButton:YES and a popup list containing zoom values will be added to the horizontal scroller. Zooming is implemented by calling zoomTo:(float)zoomX :(float)zoomY on the docView(and topView, leftView, syncViews, horizSyncViews and vertSyncViews). If the docView does not respond to this method or returns NO from this method, automatic zooming is performed by scaling the clipView by the zoom amount. The 'Set...' item in the zoom popup allows for arbitrary scaling. The 'Fit' menu item calculates the zoom value necessary to fit the docView entirely in the scrollview. The default zooming behavior tends to fail on documents that contain NXImages. Implement zoomTo:: in your custom view to explicitly scale NXImages (returning NO if you don't otherwise handle zoom).
  21.     
  22.     Support for adding arbitrary views to the vertical and horizontal scrollers. This is useful for adding little gadgets like a 'goto page' control inside of the horizontal scroller. The page up/down & left/right buttons as well as the zoom button use this facility. Page left/right is assumed to be the first in the horizScrollerViews list if it exists. The zoom button is next. Other views should be added at [horizScrollerViews count]. Page up/down is assumed to be first in the vertScrollerViews list. Scroller views are temporarily removed in reverse order if the scroller is not long enough to accommodate them.
  23.     
  24.     The PAScrollViewDeluxe palette allows you to create a PAScrollViewDeluxe by command clicking the 'Group in ScrollView' menu item. The code is a complete hack inside of the PAScrollViewDeluxeInspector code (at the bottom) and introduces a bug into IB that you can no longer drag the default TextObject/ScrollView in (it just disapears).
  25.  
  26. Copyright 1992. Jeff Martin. (jmartin@next.com 415-780-3833)
  27. ******************************************************************************/
  28.  
  29. @implementation PAScrollViewDeluxe
  30.  
  31. - initFrame:(const NXRect *)rect
  32. {
  33.     char supportNibPath[MAXPATHLEN];
  34.     NXSize defaultRulerSize = { 23, 23 };
  35.     NXRect bogusFrame = {0,0,0,0};
  36.     
  37.     [super initFrame:rect];
  38.     
  39.     // The following nib has the page up/down buttons & tiffs, zoom button and
  40.     //    the zoom panel.
  41.     [[NXBundle bundleForClass:[self class]] getPath:supportNibPath 
  42.         forResource:"PAScrollViewDeluxe" ofType:"nib"];
  43.     [NXApp loadNibFile:supportNibPath owner:self withNames:NO 
  44.         fromZone:[self zone]];
  45.         
  46.     // Hack to work around IB crasher caused by "Group in ScrollView" hack.
  47.     if(rect) NXSetRect(&bogusFrame,0,0, NX_WIDTH(rect)-23, NX_HEIGHT(rect)-23);
  48.     [self setDocView:[[View alloc] initFrame:&bogusFrame]];
  49.  
  50.     // Set reasonable defaults
  51.     [self setHorizScrollerRequired:YES];
  52.     [self setVertScrollerRequired:YES];
  53.     [self setPageScroll:20];
  54.     [self setPageUpDownButtonsVisible:YES];
  55.     [self setPageLeftRightButtonsVisible:YES];
  56.     [self setZoomButtonVisible:YES];
  57.     [self setBorderType:NX_BEZEL];
  58.     [self setBackgroundColor:NX_COLORLTGRAY];
  59.     [self setRulerClass:[Ruler class]];
  60.     [self setRulerSize:defaultRulerSize];
  61.     [self showRulers:self];
  62.     
  63.     return self;
  64. }
  65.  
  66. /******************************************************************************    
  67.     topView, setTopView:view
  68.  
  69.     topView returns the current topView. If none exists, it allocates a view of ruler class, sets it to be the topView and returns it.
  70.     setTopView: places the given view inside the scrollview (inside a clip view, topClip) at the top of the scroll view at its origional hieght but at the width of the docView. It returns the oldTopView.
  71. ******************************************************************************/
  72. - topView
  73. {
  74.     // If there isn't a topView, alloc and set rulerClass instance as default
  75.     if(!topView) {
  76.         NXRect rulerFrame = {0,0,0,0};
  77.         rulerFrame.size = [self rulerSize];
  78.         NX_WIDTH(&rulerFrame) = [contentView frameWidth];
  79.         [self setTopView:[[[self rulerClass] alloc] initFrame:&rulerFrame]];
  80.         if([topView respondsTo:@selector(setHorizontal)])
  81.             [topView setHorizontal];
  82.  
  83.     }
  84.     return topView;
  85. }
  86. - setTopView:view
  87. {
  88.     id old = topView;
  89.  
  90.     // Set the new topView, its ruler height, and remove it from where it was
  91.     topView = view;
  92.     rulerSize.height = [view frameHeight];
  93.     [topView removeFromSuperview];
  94.  
  95.     // Install it if need be
  96.     if([self topViewVisible]) 
  97.         { [self setTopViewVisible:NO]; [self setTopViewVisible:YES]; }
  98.     return old;
  99. }
  100.  
  101. /******************************************************************************
  102.     (BOOL)topViewVisible, setTopViewVisible:(BOOL)flag
  103.     
  104.     topViewVisible returns whether or not the topView is visible.
  105.     setTopViewVisible: will install the current topView inside a clipView on top of the docView if set to YES and will remove the existing topView if set to NO. Retiles the views, but does not call display.Returns self.
  106. ******************************************************************************/
  107. - (BOOL)topViewVisible { return topViewVisible; }
  108. - setTopViewVisible:(BOOL)flag;
  109. {
  110.     // If the new state is equal to the old, return
  111.     if(flag == topViewVisible) return self;
  112.     
  113.     topViewVisible = flag;
  114.     
  115.     // If we are setting topView to visible add it to hierarchy
  116.     if(topViewVisible) {
  117.     
  118.         // Allocate clipView to scroll leftView, and build view hierarchy
  119.         topClip = [[[ClipView alloc] init] setAutosizing:NX_WIDTHSIZABLE];
  120.         [self addSubview:topClip];
  121.         [topClip setDocView:[self topView]];
  122.     }
  123.     else {
  124.         // Remove topView and topClip. Free topClip and set it to NULL.
  125.         [[self topView] removeFromSuperview];
  126.         [topClip removeFromSuperview];
  127.         [topClip free]; topClip = NULL;
  128.     }
  129.     
  130.     // Retile to fix layout of views and synchronize topView to docView
  131.     [self tile];
  132.     if(topViewVisible) [self synchronizeClipView:topClip 
  133.         withClipView:contentView horizontally:YES vertically:NO];
  134.     return self;
  135. }
  136.  
  137. /******************************************************************************
  138.     showTopView:sender, hideTopView:sender toggleTopView:sender
  139.  
  140.     These convenience methods simply wrap around setTopViewVisible: and can be set to be called from menus or controls inside of InterfaceBuilder.
  141. ******************************************************************************/
  142. - showTopView:sender
  143. { [self setTopViewVisible:YES]; [self display]; return self; }
  144. - hideTopView:sender
  145. { [self setTopViewVisible:NO]; [self display]; return self; }
  146. - toggleTopView:sender 
  147. { [self setTopViewVisible:!topViewVisible]; [self display]; return self; }
  148.  
  149. /******************************************************************************    
  150.     leftView, setLeftView: - query and set the "left view".
  151.  
  152.     leftView returns the current leftView. If none exists, it allocates a view of ruler class, sets it to be the leftView and returns it.
  153.     setLeftView: places the given view inside the scrollview (inside a clip view, leftClip) at the left of the scroll view at its origional width but at the height of the docView. It returns the oldLeftView.
  154. ******************************************************************************/
  155. - leftView
  156. {
  157.     // If there isn't a leftView, alloc and set rulerClass instance as default
  158.     if(!leftView) {
  159.         NXRect rulerFrame = {0,0,0,0};
  160.         rulerFrame.size = [self rulerSize];
  161.         NX_HEIGHT(&rulerFrame) = [contentView frameHeight];
  162.         [self setLeftView:[[[self rulerClass] alloc] initFrame:&rulerFrame]];
  163.         if([leftView respondsTo:@selector(setVertical)])
  164.             [leftView setVertical];
  165.     }
  166.     return leftView;
  167. }
  168. - setLeftView:view
  169. {
  170.     id old = leftView;
  171.  
  172.     // Set the new leftView, its ruler Width, and remove it from where it was
  173.     leftView = view;
  174.     rulerSize.width = [view frameWidth];
  175.     [leftView removeFromSuperview];
  176.     
  177.     // Install leftView if necessary
  178.     if([self leftViewVisible])
  179.         { [self setLeftViewVisible:NO]; [self setLeftViewVisible:YES]; }
  180.     return old;
  181. }
  182.  
  183.  
  184. /******************************************************************************
  185.     leftViewVisible, setLeftViewVisible - query & set whether leftView is visible
  186.     
  187.     leftViewVisible returns whether or not the leftView is visible.
  188.     setLeftViewVisible: will install the current leftView inside a clipView on left of the docView if set to YES and will remove the existing leftView if set to NO. Retiles the views, but does not call display. Returns self.
  189. ******************************************************************************/
  190. - (BOOL)leftViewVisible { return leftViewVisible; }
  191. - setLeftViewVisible:(BOOL)flag;
  192. {
  193.     // If the new state is equal to the old, return
  194.     if(flag == leftViewVisible) return self;
  195.     
  196.     leftViewVisible = flag;
  197.     
  198.     // If we are setting leftView to visible add it to hierarchy
  199.     if(leftViewVisible) {
  200.     
  201.         // Allocate clipView to scroll leftView, and build view hierarchy
  202.         leftClip = [[[ClipView alloc] init] setAutosizing:NX_WIDTHSIZABLE];
  203.         [self addSubview:leftClip];
  204.         [leftClip setDocView:[self leftView]];
  205.     }
  206.     else {
  207.         // Remove leftView and leftClip. Free leftClip and set it to NULL.
  208.         [[self leftView] removeFromSuperview];
  209.         [leftClip removeFromSuperview];
  210.         [leftClip free]; leftClip = NULL;
  211.     }
  212.     
  213.     // Retile to fix layout of views and synchronize leftView to docView
  214.     [self tile];
  215.     if(leftViewVisible) [self synchronizeClipView:leftClip 
  216.         withClipView:contentView horizontally:NO vertically:YES];
  217.     return self;
  218. }
  219.  
  220. /******************************************************************************
  221.     showLeftView:, hideLeftView: toggleLeftView:
  222.  
  223.     These convenience methods simply wrap around setLeftViewVisible: and can be set to be called from menus or controls inside of InterfaceBuilder.
  224. ******************************************************************************/
  225. - showLeftView:sender
  226. { [self setLeftViewVisible:YES]; [self display]; return self; }
  227. - hideLeftView:sender
  228. { [self setLeftViewVisible:NO]; [self display]; return self; }
  229. - toggleLeftView:sender 
  230. { [self setLeftViewVisible:!leftViewVisible]; [self display]; return self; }
  231.  
  232. /******************************************************************************
  233.     showRulers:, hideRuler: toggleRulers:
  234.     
  235.     These are convenience methods for showing/hiding/toggling top and left views as a pair. They wrap around the setTopViewVisible and setLeftViewVisible. These methods can be set to be called from menus or controls inside of InterfaceBuilder. They all return self.
  236. ******************************************************************************/
  237. - showRulers:sender 
  238. {
  239.     [window disableDisplay];
  240.     [self setTopViewVisible:YES]; [self setLeftViewVisible:YES];
  241.     [window reenableDisplay]; [self display];
  242.     return self;
  243. }
  244. - hideRulers:sender
  245. {
  246.     [window disableDisplay];
  247.     [self setTopViewVisible:NO]; [self setLeftViewVisible:NO];
  248.     [window reenableDisplay]; [self display];
  249.     return self;
  250. }
  251. - toggleRulers:sender
  252. {
  253.     [window disableDisplay];
  254.     
  255.     // If visible set to not visible and visa-versa.
  256.     [self setTopViewVisible:![self topViewVisible]];
  257.     [self setLeftViewVisible:![self leftViewVisible]];
  258.  
  259.     [window reenableDisplay];
  260.     [self display];
  261.     return self;
  262. }
  263.  
  264. /******************************************************************************
  265.     rulerClass, setRulerClass:(Class)class
  266.     
  267.     If the PAScrollView deluxe is asked to show top or left views when none has been set, it attempts to allocate an instance of 'rulerClass' (assumed to be a view). If the instance responds to setHorizontal or setVertical, this will be called.
  268. ******************************************************************************/
  269. - (Class)rulerClass { return rulerClass; }
  270. - setRulerClass:(Class)class { rulerClass = class; return self; }
  271.  
  272. /******************************************************************************
  273.     rulerSize, setRulerSize:(NXSize)size
  274.     
  275.     When PAScrollViewDeluxe allocates a default top/left view, it sets the top one to be of height rulerSize.height and the left one to be of width rulerSize.width. If a topView or leftView are added programatically, the rulerSize.height and rulerSize.width are set respectively.
  276. ******************************************************************************/
  277. - (NXSize)rulerSize { return rulerSize; }
  278. - setRulerSize:(NXSize)size { rulerSize = size; return self; }
  279.  
  280. /******************************************************************************
  281.     syncView, addSyncView:at:, removeSyncView:, removeSyncViewAt:
  282.     horizSyncViews, addHorizSyncView:at:, removeHorizSyncView: & ViewAt:
  283.     vertSyncViews, addVertSyncView:at:, removeVertSyncView: & ViewAt:
  284.         
  285.     syncViews are ScrollViews that are to be scrolled, sized and (optionally)zoomed with respect to the docViews position, size and zoom. horizSyncViews are only affected in the horizontal direction, while vertSyncViews are only affected in the vertical direction. Group a view inside of a ScrollView, disable its horizontal and vertical scrollers, and use one of the addMethods. In IB you can actually set one of each type view by setting the syncViews, horizSyncViews or vertSyncViews outlet to a ScrollView. It will be added to the list when the outlets are set.
  286.     The syncViews, horizSyncViews and vertSyncViews methods return the list of the views that are currently being syncronized in the respective direction(can be NULL if there are none).
  287.     addSyncView:at:, addHorizSyncView:at: and addVertSyncView:at: add scrollviews to be synchronized in the respective direction at the given location in the list(use [[myPASV syncViews] count] to add to end). Returns self.
  288.     removeSyncView:, removeHorizSyncView:, removeVertSyncView: remove the given view from its respective list by calling removeSyncAt: with the view's index.
  289.     removeSyncViewAt:, removeHorizSyncViewAt: and removeVertSyncViewAt: remove ScrollViews from the respective list of syncronized ScrollViews. Returns self.
  290. ******************************************************************************/
  291. - syncViews { return syncViews; }
  292. - addSyncView:view at:(int)at
  293. {
  294.     // Only add ScrollViews
  295.     if([view isKindOfClassNamed:"ScrollView"]) {
  296.         if(!syncViews) syncViews = [[List alloc] init];
  297.         [syncViews addObjectIfAbsent:view];
  298.     }
  299.     return self;
  300. }
  301. - removeSyncView:view
  302. { return [self removeSyncViewAt:[[self syncViews] indexOf:view]]; }
  303. - removeSyncViewAt:(int)at { [syncViews removeObjectAt:at]; return self; }
  304.  
  305. - horizSyncViews { return horizSyncViews; }
  306. - addHorizSyncView:view at:(int)at
  307. {
  308.     // Only add ScrollViews
  309.     if([view isKindOfClassNamed:"ScrollView"]) {
  310.         if(!horizSyncViews) horizSyncViews = [[List alloc] init];
  311.         [horizSyncViews addObjectIfAbsent:view];
  312.     }
  313.     return self;
  314. }
  315. - removeHorizSyncView:view
  316. { return [self removeHorizSyncViewAt:[[self horizSyncViews] indexOf:view]]; }
  317. - removeHorizSyncViewAt:(int)at { [horizSyncViews removeObjectAt:at]; return self; }
  318.  
  319. - vertSyncViews { return vertSyncViews; }
  320. - addVertSyncView:view at:(int)at
  321. {
  322.     // Only add ScrollViews
  323.     if([view isKindOfClassNamed:"ScrollView"]) {
  324.         if(!vertSyncViews) vertSyncViews = [[List alloc] init];
  325.         [vertSyncViews addObjectIfAbsent:view];
  326.     }
  327.     return self;
  328. }
  329. - removeVertSyncView:view
  330. { return [self removeVertSyncViewAt:[[self vertSyncViews] indexOf:view]]; }
  331. - removeVertSyncViewAt:(int)at { [vertSyncViews removeObjectAt:at]; return self; }
  332.  
  333. /******************************************************************************
  334.     horizScrollerViews, addHorizScrollerView:at:, removeHorizScrollerView:
  335.     vertScrollerViews, addVertScrollerView:at:, removeVertScrollerView:
  336.         
  337.     ScrollerViews are views embedded inside of the vertical or horizontal scrollers. The are frequently simple controls like a "Goto Page:" control. In fact the page up/down & left/right buttons as well as the zoomButton are horizScrollerViews (assumed to be at 0 and 1 respectively if they exist).When added these views are sized to fit into the scroller(ie, horizontal scroller views are constrained to the horizontal scroller's height).
  338.     The horizScrollerViews and vertScrollerViews methods return the list of the views that are currently in the respective scroller (can be NULL if there are none).
  339.     addHorizScrollerView:at: and addVertScrollerView:at: add a view to their respective list at the given location(use [[myPASV vertSyncViews] count] to add to end). They returns self.
  340.     removeHorizScrollerView: and removeVertScrollerView: removes the given view from the respective scroller list. Returns self.
  341.     removeHorizScrollerViewAt: and removeVertScrollerViewAt: removes the view at the given location from the respective scroller list. Returns self.
  342. ******************************************************************************/
  343. - horizScrollerViews { return horizScrollerViews; }
  344. - addHorizScrollerView:view at:(int)at
  345. {
  346.     // Make sure the list exists and add the view
  347.     if(!horizScrollerViews) horizScrollerViews = [[List alloc] init];
  348.     [horizScrollerViews insertObject:view at:at];
  349.  
  350.     // Add the view to subview list and retile
  351.     [self addSubview:view]; [self tile];
  352.     return self;
  353. }
  354. - removeHorizScrollerView:view
  355. {
  356.     [self removeHorizScrollerViewAt:[[self horizScrollerViews] indexOf:view]];
  357.     return self;
  358. }
  359. - removeHorizScrollerViewAt:(int)at
  360. {
  361.     [[horizScrollerViews removeObjectAt:at] removeFromSuperview];
  362.     [self tile];
  363.     return self;
  364. }
  365.  
  366. - vertScrollerViews { return vertScrollerViews; }
  367. - addVertScrollerView:view at:(int)at
  368. {
  369.     // Make sure the list exists and add the view.
  370.     if(!vertScrollerViews) vertScrollerViews = [[List alloc] init];
  371.     [vertScrollerViews insertObject:view at:at];
  372.  
  373.     // Add the view to subview list and retile
  374.     [self addSubview:view]; [self tile];
  375.     return self;
  376. }
  377. - removeVertScrollerView:view
  378. {
  379.     [self removeVertScrollerViewAt:[[self vertScrollerViews] indexOf:view]];
  380.     return self;
  381. }
  382. - removeVertScrollerViewAt:(int)at
  383. {
  384.     [[vertScrollerViews removeObjectAt:at] removeFromSuperview];
  385.     [self tile];
  386.     return self;
  387. }
  388.  
  389. /******************************************************************************
  390.     pageUpDownButtons, pageLeftRightButtons, zoomButton
  391.     
  392.     These methods return pointers to the matrices that contain the up/down & left/right buttons. They are loaded in at init time from PAScrollViewDeluxe.nib.
  393. ******************************************************************************/ 
  394. - pageUpDownButtons { return pageUpDownButtons; }
  395. - pageLeftRightButtons { return pageLeftRightButtons; }
  396. - zoomButton { return zoomButton; }
  397.  
  398. /******************************************************************************
  399.     pageUpDownButtonsVisible, setPageUpDownButtonsVisible:, needUpDownButtons
  400.     pageLeftRightButtonsVisible, setPageLeftRightButtonsVisible
  401.     needPageLeftRightButtons
  402.     
  403.     These methods query and set whether the respective button set is visible.
  404.     The setButtonsVisible method calls the respective add or remove scrollerView method with the 'at' value equal to zero.
  405.     The needPageButtons methods return whether the page buttons are actually needed (ie, if the docView is smaller than the contentView or the scrollable area is less than 2 pages, the buttons are not needed).
  406. ******************************************************************************/ 
  407. - (BOOL)pageUpDownButtonsVisible { return pageUpDownButtonsVisible; }
  408. - setPageUpDownButtonsVisible:(BOOL)flag
  409. {
  410.     // If this is a new state then either add or remove buttons from scroller
  411.     if(pageUpDownButtonsVisible != flag) {
  412.         pageUpDownButtonsVisible = flag;
  413.         if(flag) [self addVertScrollerView:[self pageUpDownButtons] at:0];
  414.         else [self removeVertScrollerView:[self pageUpDownButtons]];
  415.     }
  416.     return self;
  417. }
  418. - (BOOL)needPageUpDownButtons { return (([vScroller perCent] < .5) && 
  419.     ([contentView boundsHeight] < [[contentView docView] frameHeight])); }
  420.  
  421. - (BOOL)pageLeftRightButtonsVisible { return pageLeftRightButtonsVisible; }
  422. - setPageLeftRightButtonsVisible:(BOOL)flag
  423. {
  424.     // If this is a new state then either add or remove buttons from scroller
  425.     if(pageLeftRightButtonsVisible != flag) {
  426.         pageLeftRightButtonsVisible = flag;
  427.         if(flag) [self addHorizScrollerView:[self pageLeftRightButtons] at:0];
  428.         else [self removeHorizScrollerView:[self pageLeftRightButtons]];
  429.     }
  430.     return self;
  431. }
  432. - (BOOL)needPageLeftRightButtons { return (([hScroller perCent] < .5) && 
  433.     ([contentView boundsWidth] < [[contentView docView] frameWidth])); }
  434.  
  435. /******************************************************************************
  436.     pageButton
  437.  
  438.     This method is the target to all of the page up/down/left/right buttons. Based on the tags, it scrolls the visible rect by its extents minus the page overlap (pageContext) in the respective direction. Returns self.
  439. ******************************************************************************/ 
  440. - pageButton:sender
  441. {
  442.     NXRect rect;
  443.     int flipped;
  444.     NXSize pageContextSize = {pageContext, pageContext};
  445.     NXCoord newPageCont;
  446.     
  447.     // Get the docView's visible rect in docView's coords.
  448.     [[self docView] getVisibleRect:&rect];
  449.     [[self docView] convertRectFromSuperview:&rect];
  450.  
  451.     // Convert the ScrollViews pageContext to docView(to correct for zooming)
  452.     [[self docView] convertSize:&pageContextSize fromView:self];
  453.     newPageCont = pageContextSize.width;
  454.     
  455.     flipped = [[self docView] isFlipped] ? -1 : 1;
  456.  
  457.     // Move the visible rect in the respective direction (up/down/left/right
  458.     //    respectively). Allow for flippedness and page overlap.
  459.     switch([sender selectedTag]) {
  460.         case 0: NX_Y(&rect) += (NX_HEIGHT(&rect) - newPageCont)*flipped; break;
  461.         case 1: NX_Y(&rect) -= (NX_HEIGHT(&rect) - newPageCont)*flipped; break;
  462.         case 2: NX_X(&rect) -= NX_WIDTH(&rect) - newPageCont; break;
  463.         case 3: NX_X(&rect) += NX_WIDTH(&rect) - newPageCont; break;
  464.     }
  465.     
  466.     // Scroll the new rect to visible.
  467.     [[self docView] scrollRectToVisible:&rect];
  468.     return self;
  469. }
  470.  
  471. /******************************************************************************
  472.     zoomButtonVisible, setZoomButtonVisible:
  473.  
  474.     These methods query and set whether the zoom button is visible.
  475.     setZoomButtonVisible: either adds the zoomButton to the vert scroller via - addHorizScrollerView: or removes via removeHorizScrollerView. Returns self.
  476. ******************************************************************************/
  477. - (BOOL)zoomButtonVisible { return zoomButtonVisible; }
  478. - setZoomButtonVisible:(BOOL)flag
  479. {
  480.     // If this is a new state then either add or remove button from scroller
  481.     if(zoomButtonVisible != flag) {
  482.         zoomButtonVisible = flag;
  483.         if(zoomButtonVisible) {
  484.             if(pageLeftRightButtonsVisible)
  485.                 [self addHorizScrollerView:[self zoomButton] at:1];
  486.             else [self addHorizScrollerView:[self zoomButton] at:0];
  487.         }
  488.         else [self removeHorizScrollerView:[self zoomButton]];
  489.     }
  490.     return self;
  491. }
  492.  
  493. /******************************************************************************
  494.     zoom:
  495.  
  496.     This method is called by the zoomButton's popUpList to get the zoom amount. It reads the title and converts it to a scale (special cased for 'Set...' and 'Size To Fit'). Calls zoomTo::.
  497. ******************************************************************************/
  498. #define CLAMP(a,x,y) (MAX((x), MIN((y), (a))))
  499. - zoom:sender
  500. {
  501.     float scaleX, scaleY;
  502.     char *title;
  503.     
  504.     // Get the title of the selected menu item
  505.     title = NXCopyStringBuffer([[sender selectedCell] title]);
  506.  
  507.     // If it is "Fit" (tag == 6) the simply sizeTo:: to get size to fit...
  508.     if([sender selectedTag] == 6) {
  509.         NXRect docViewBounds, contentFrame;
  510.         [[self docView] getBounds:&docViewBounds];
  511.         [contentView getFrame:&contentFrame];
  512.         // Zoom so that docView's bounds == content's frame
  513.         scaleX = NX_WIDTH(&contentFrame)/(NX_WIDTH(&docViewBounds)+1);
  514.         scaleY = NX_HEIGHT(&contentFrame)/(NX_HEIGHT(&docViewBounds)+1);
  515.     }
  516.  
  517.     // Otherwise if it is "Set..." (tag == 7) then run the panel...
  518.     else if([sender selectedTag] == 7) {
  519.             static char string[32];
  520.             
  521.             [[zoomButton target] removeItem:string];
  522.             [zoomText selectText:self];
  523.             [NXApp runModalFor:zoomPanel];
  524.             [zoomPanel close];
  525.             scaleX = CLAMP([zoomText intValue],10,1600);
  526.             sprintf(string, "%d%%", (int)scaleX);
  527.             scaleX = scaleY = scaleX/100.0;
  528.             
  529.             // Add the value to the button (lazily since there is a problem
  530.             //    doing it interactively, and reset the popUp's action to zoom.
  531.             [zoomButton perform:@selector(setTitle:) with:(id)string 
  532.                 afterDelay:0 cancelPrevious:YES];
  533.             [[[zoomButton target] setTarget:self] setAction:@selector(zoom:)];
  534.     }
  535.     
  536.     // Otherwise convert the title from percentage to scale value
  537.     else {
  538.         title[strlen(title)-1] = '\0';
  539.         scaleX = scaleY = atoi(title)/100.0;
  540.     }
  541.     free(title);
  542.  
  543.     [self zoomTo:scaleX :scaleY];
  544.     return self;
  545. }
  546.  
  547. // This method is used to stop the zoomPanels modal state.
  548. - stopZoomPanel:sender { [NXApp stopModal]; return self; }
  549.  
  550. /******************************************************************************
  551.     zoomTo:(float)zoomX :(float)zoomY
  552.  
  553.     This method tries to call zoomTo:: on the docView and all of the accessory views (topView, leftView, syncViews, horizSyncViews, vertSyncViews) with the given scale (1 is full size). If the views implement zoomTo:: and actually do the zoom, they should return YES. If they just implement zoomTo:: to get notification of a zoom or to scale dependent pieces(like NXImages), they should return NO. If zoomTo:: is not implemented or returns NO, automatic scaling takes place(on the ClipView).
  554. ******************************************************************************/
  555. - (BOOL)zoomTo:(float)scaleX :(float)scaleY
  556. {
  557.     NXRect docViewVis, docViewVis2;
  558.     int i,j;
  559.  
  560.     // Disable display so we don't see scales and scrolls
  561.     [window disableDisplay];
  562.  
  563.     // Get the current visible docView rect(to preserve current view)
  564.     [[self docView] getVisibleRect:&docViewVis];
  565.     [[self docView] convertRectFromSuperview:&docViewVis];
  566.     
  567.     // Zoom the docView
  568.     if(!([[contentView docView] respondsTo:@selector(zoomTo::)] &&
  569.         [[contentView docView] zoomTo:scaleX :scaleY])) {
  570.         [contentView setDrawSize:[contentView frameWidth] 
  571.             :[contentView frameHeight]];
  572.         [contentView scale:scaleX :scaleY];
  573.         [self reflectScroll:contentView];
  574.     }
  575.  
  576.     // Zoom the topView
  577.     if(!([topView respondsTo:@selector(zoomTo::)] &&
  578.         [topView zoomTo:scaleX :scaleY])) {
  579.         [topClip setDrawSize:[topClip frameWidth] :[topClip frameHeight]];
  580.         [topClip scale:scaleX :1];
  581.     }
  582.     
  583.     // Zoom the leftView
  584.     if(!([leftView respondsTo:@selector(zoomTo::)] &&
  585.         [leftView zoomTo:scaleX :scaleY])) {
  586.         [leftClip setDrawSize:[leftClip frameWidth] :[leftClip frameHeight]];
  587.         [leftClip scale:1 :scaleY];
  588.     }
  589.     
  590.     // Zoom the synchronized Views.
  591.     for(i=0; i<[syncViews count]; i++) {
  592.         id subviewList = [[syncViews objectAt:i] subviews];
  593.         
  594.         // Look inside each scrollview for its clipviews
  595.         for(j=0; j<[subviewList count]; j++) {
  596.             id view = [subviewList objectAt:j];
  597.             
  598.             // Only zoom the ClipViews of the scrollViews
  599.             if([view isKindOf:[ClipView class]]) {
  600.             
  601.                 // If view responds NO to zoomTo: scale the ClipView
  602.                 if(!([[view docView] respondsTo:@selector(zoomTo::)] &&
  603.                     [[view docView] zoomTo:scaleX :scaleY])) {
  604.                     [view setDrawSize:[view frameWidth] :[view frameHeight]];
  605.                     [view scale:scaleX :scaleY];
  606.                     
  607.                     // Only reflect Scroll the docView
  608.                     if([view docView] == [[view superview] docView])
  609.                         [[view superview] reflectScroll:view];
  610.                 }
  611.             }
  612.         }
  613.         [[horizSyncViews objectAt:i] display];
  614.     }
  615.     
  616.     // Zoom the horizontally synchronized Views.
  617.     for(i=0; i<[horizSyncViews count]; i++) {
  618.         id subviewList = [[horizSyncViews objectAt:i] subviews];
  619.         
  620.         // Look inside each scrollview for its clipviews
  621.         for(j=0; j<[subviewList count]; j++) {
  622.             id view = [subviewList objectAt:j];
  623.             
  624.             // Only zoom the ClipViews of the scrollViews
  625.             if([view isKindOf:[ClipView class]]) {
  626.             
  627.                 // If view responds NO to zoomTo: scale the ClipView
  628.                 if(!([[view docView] respondsTo:@selector(zoomTo::)] &&
  629.                     [[view docView] zoomTo:scaleX :1])) {
  630.                     [view setDrawSize:[view frameWidth] :[view frameHeight]];
  631.                     [view scale:scaleX :1];
  632.                     
  633.                     // Only reflect Scroll the docView
  634.                     if([view docView] == [[view superview] docView])
  635.                         [[view superview] reflectScroll:view];
  636.                 }
  637.             }
  638.         }
  639.         [[horizSyncViews objectAt:i] display];
  640.     }
  641.     
  642.     // Zoom the vertically synchronized views.
  643.     for(i=0; i<[vertSyncViews count]; i++) {
  644.         id subviewList = [[vertSyncViews objectAt:i] subviews];
  645.         
  646.         // Look inside each scrollview for its clipviews
  647.         for(j=0; j<[subviewList count]; j++) {
  648.             id view = [subviewList objectAt:j];
  649.             
  650.             // Only zoom the ClipViews of the scrollViews
  651.             if([view isKindOf:[ClipView class]]) {
  652.             
  653.                 // If view responds NO to zoomTo: scale the ClipView
  654.                 if(!([[view docView] respondsTo:@selector(zoomTo::)] &&
  655.                     [[view docView] zoomTo:1 :scaleY])) {
  656.                     [view setDrawSize:[view frameWidth] :[view frameHeight]];
  657.                     [view scale:1 :scaleY];
  658.                     
  659.                     // Only reflect Scroll the docView
  660.                     if([view docView] == [[view superview] docView])
  661.                         [[view superview] reflectScroll:view];
  662.                 }
  663.             }
  664.         }
  665.         [[vertSyncViews objectAt:i] display];
  666.     }
  667.     
  668.     // Get new visible size and inset old visible rect by the difference
  669.     [[self docView] getVisibleRect:&docViewVis2];
  670.     [[self docView] convertRectFromSuperview:&docViewVis2];
  671.     NXInsetRect(&docViewVis, 
  672.         (NX_WIDTH(&docViewVis) - NX_WIDTH(&docViewVis2))/2, 
  673.         (NX_HEIGHT(&docViewVis) - NX_HEIGHT(&docViewVis2))/2);
  674.         
  675.     // Scroll to new rect (as much to the center of the old rect as possible)
  676.     [[self docView] scrollRectToVisible:&docViewVis];
  677.     
  678.     // Renable display and display final change
  679.     [window reenableDisplay]; [window display];
  680.  
  681.     return YES;
  682. }    
  683.  
  684. /******************************************************************************
  685.     scrollClip:to:
  686.  
  687.     This method is called automatically whenever any of the clipView subviews are scrolled. We intercept it so that we can perform the scroll on all of the dependant views(topView, leftView, syncViews, horizSyncViews & vertSyncViews). Returns Self.
  688. ******************************************************************************/
  689. - scrollClip:(ClipView *)clipView to:(const NXPoint *)aPoint
  690. {
  691.     int i,j;
  692.     
  693.     // RawScroll the given clipView to given point
  694.     [clipView rawScroll:aPoint];
  695.  
  696.     // Scroll the docView
  697.     if(contentView != clipView)
  698.         [self synchronizeClipView:contentView withClipView:clipView
  699.             horizontally:(clipView==topClip) vertically:(clipView==leftClip)];
  700.     
  701.     // Synchronize top ruler if visible and wasn't the given clipView
  702.     if (topViewVisible && (topClip != clipView))
  703.         [self synchronizeClipView:topClip withClipView:contentView
  704.             horizontally:YES vertically:NO];
  705.     
  706.     // Synchronize left ruler if visible and wasn't the given clipView
  707.     if(leftViewVisible && (leftClip != clipView))
  708.         [self synchronizeClipView:leftClip withClipView:contentView
  709.             horizontally:NO vertically:YES];
  710.  
  711.     // Scroll syncViews
  712.     for(i=0; i<[syncViews count]; i++) {
  713.         id view = [syncViews objectAt:i];
  714.         id subViews = [view subviews];
  715.         
  716.         // RawScroll the ClipView subViews, reflect scroll the contentView
  717.         for(j=0; j<[subViews count]; j++) {
  718.             id subView = [subViews objectAt:j];
  719.             if([subView isKindOf:[ClipView class]]) {
  720.                 [self synchronizeClipView:subView withClipView:contentView
  721.                     horizontally:YES vertically:YES];
  722.                 if([view docView] == [subView docView])
  723.                     [view reflectScroll:subView];
  724.             }
  725.         }
  726.     }
  727.     
  728.     // Scroll horizSyncViews
  729.     for(i=0; i<[horizSyncViews count]; i++) {
  730.         id view = [horizSyncViews objectAt:i];
  731.         id subViews = [view subviews];
  732.         
  733.         // horiz RawScroll the ClipView subViews, reflect scroll contentView
  734.         for(j=0; j<[subViews count]; j++) {
  735.             id subView = [subViews objectAt:j];
  736.             if([subView isKindOf:[ClipView class]]) {
  737.                 [self synchronizeClipView:subView withClipView:contentView
  738.                     horizontally:YES vertically:NO];
  739.                 if([view docView] == [subView docView])
  740.                     [view reflectScroll:subView];
  741.             }
  742.         }
  743.     }
  744.     
  745.     // Scroll vertSyncViews
  746.     for(i=0; i<[vertSyncViews count]; i++) {
  747.         id view = [vertSyncViews objectAt:i];
  748.         id subViews = [view subviews];
  749.         
  750.         // vert RawScroll the ClipView subViews, reflect scroll the contentView
  751.         for(j=0; j<[subViews count]; j++) {
  752.             id subView = [subViews objectAt:j];
  753.             if([subView isKindOf:[ClipView class]]) {
  754.                 [self synchronizeClipView:subView withClipView:contentView
  755.                     horizontally:NO vertically:YES];
  756.                 if([view docView] == [subView docView])
  757.                     [view reflectScroll:subView];
  758.             }
  759.         }
  760.     }
  761.  
  762.     return self;
  763. }
  764.  
  765.  
  766. /******************************************************************************
  767.     synchronizeClipView:withClipView:horizontally:vertically:
  768.  
  769.     This method sets the first clipView so that it is viewing as much of the same rect that the withClipView is viewing as possible. It figures out this rect in the withClipView, corrects for coordinate system differences and flippedness and does a rawScroll in the given clipView.
  770.     The horizontally and vertically flags allow the synchronization to be constrained to a particular direction.
  771.     This method is called within scrollClip:to: to synchronize the various parts of the ScrollView (rulers, etc). You will probably never call it directly. Returns self.
  772. ******************************************************************************/
  773. - synchronizeClipView:newClip withClipView:oldClip
  774.     horizontally:(BOOL)horizSync vertically:(BOOL)vertSync
  775. {
  776.     // Get the offset of the oldClip's origin from its docView's origin 
  777.     float dx = [oldClip boundsX] - [[oldClip docView] frameX];
  778.     float dy = [oldClip boundsY] - [[oldClip docView] frameY];
  779.     NXPoint point;
  780.     
  781.     // Calc new X and Y by adding offset to newClips's docView's origin
  782.     point.x = [[newClip docView] frameX] + dx;
  783.     point.y = [[newClip docView] frameY] + dy;
  784.  
  785.     // If the flippedness is not the same, take the compliment of y
  786.     if([oldClip isFlipped] != [newClip isFlipped])
  787.         point.y = [[newClip docView] frameY] +
  788.         [[oldClip docView] frameHeight] - (dy + [newClip boundsHeight]);
  789.     
  790.     // Constrain scrolling from given flags
  791.     if(!horizSync) point.x = [newClip boundsX];
  792.     if(!vertSync)  point.y = [newClip boundsY];
  793.     
  794.     // Scroll to new boundsOrigin
  795.     [newClip rawScroll:&point];
  796.     return self;
  797. }
  798.             
  799.             /******************************************************************************
  800.     reflectScroll
  801.  
  802.     This method is called to adjust the scrollers when there has been a change to the docView (it is called automatically durring autoscroll or when the docView is resized). We override it so that we can add or remove the page buttons if necessary. Returns self.
  803. ******************************************************************************/
  804. - reflectScroll:view
  805. {    
  806.     BOOL newUpDown, oldUpDown = [self needPageUpDownButtons];
  807.     BOOL newLeftRight,  oldLeftRight = [self needPageLeftRightButtons];
  808.  
  809.     [super reflectScroll:contentView];
  810.     
  811.     newUpDown = [self needPageUpDownButtons];
  812.     newLeftRight = [self needPageLeftRightButtons];
  813.     
  814.     // If scroller knobs appeared or disapeared, retile and display scrollers
  815.     if((newUpDown != oldUpDown) || (newLeftRight !=oldLeftRight)) {
  816.         [self tileScrollerViews];
  817.         [hScroller display]; [vScroller display];
  818.         [[self horizScrollerViews] makeObjectsPerform:@selector(display)];
  819.         [[self vertScrollerViews] makeObjectsPerform:@selector(display)];
  820.     }
  821.     return self;
  822. }
  823.  
  824. /******************************************************************************
  825.     descendantFrameChanged:
  826.  
  827.     This method is called automatically whenever the document changes its frame size. We override it so that we can grow the dependent views respectively (topView, leftView, syncView, horizSyncView, vertSyncViews).
  828. ******************************************************************************/
  829. - descendantFrameChanged:sender
  830. {
  831.     int i;
  832.     
  833.     [super descendantFrameChanged:sender];
  834.  
  835.     // Size topView to docView's new height
  836.     if(topView) [[self topView] sizeTo:[[self docView] frameWidth] 
  837.             :[[self topView] frameHeight]];
  838.     
  839.     // Size leftView to docView's new height
  840.     if(leftView) [[self leftView] sizeTo:[[self leftView] frameWidth]
  841.         :[[self docView] frameHeight]]; 
  842.  
  843.     // Size syncViews to docViews new width and hieght
  844.     for(i=0; i<[syncViews count]; i++)
  845.         [[[syncViews objectAt:i] docView] sizeTo:[[self docView] frameWidth]
  846.             :[[self docView] frameHeight]];
  847.             
  848.     // Size horizSyncViews to docViews new width
  849.     for(i=0; i<[horizSyncViews count]; i++)
  850.         [[[horizSyncViews objectAt:i] docView] 
  851.             sizeTo:[[self docView] frameWidth] 
  852.             :[[[horizSyncViews objectAt:i] docView] frameHeight]];
  853.             
  854.     // Size vertSyncViews to docViews new hieght
  855.     for(i=0; i<[vertSyncViews count]; i++)
  856.         [[[vertSyncViews objectAt:i] docView] 
  857.             sizeTo:[[[vertSyncViews objectAt:i] docView] frameWidth] 
  858.             :[[self docView] frameHeight]];
  859.             
  860.  
  861.     return self;
  862. }
  863.  
  864. /******************************************************************************
  865.     tile
  866.  
  867.     This method is where the real work of adding all of the subviews to the scrollview happens. It simply does a divide rect on the various parts and sets the origional parts to the diminished rect and the new parts to the new rect. Does not display. Returns self.
  868. ******************************************************************************/
  869. - tile
  870. {    
  871.     [super tile];
  872.  
  873.     if([self topViewVisible]) {
  874.         NXRect contentFrame = [contentView frame];
  875.         NXRect topClipFrame, cornerFrame;
  876.         
  877.         // Split contentView frame into topView part and contentView part
  878.         NXDivideRect(&contentFrame, &topClipFrame, rulerSize.height, NX_YMIN);
  879.         [contentView setFrame:&contentFrame];
  880.         
  881.         // If Both rulers exist, further divide topFrame for the corner
  882.         if([self leftViewVisible]) NXDivideRect(&topClipFrame, &cornerFrame, 
  883.             rulerSize.width, NX_XMIN);
  884.         [topClip setFrame:&topClipFrame];
  885.         
  886.         // Resize the topView to Min(docViewWidth, contentViewWidth)
  887.         [topView sizeTo:MAX([[self docView] frameWidth], 
  888.             [contentView boundsWidth]) :rulerSize.height]; 
  889.     }
  890.     
  891.     if([self leftViewVisible]) {
  892.         NXRect contentFrame = [contentView frame];
  893.         NXRect leftClipFrame;
  894.         
  895.         // Split contentView frame into leftView part and contentView part
  896.         NXDivideRect(&contentFrame, &leftClipFrame, rulerSize.width, NX_XMIN);
  897.         [contentView setFrame:&contentFrame];
  898.         [leftClip setFrame:&leftClipFrame];
  899.         
  900.         // Resize leftView to the Min(docView height, contentView height)
  901.         [leftView sizeTo:rulerSize.width 
  902.             :MAX([[self docView] frameHeight], [contentView boundsHeight])]; 
  903.     }
  904.     
  905.     // Retile the scroller views
  906.     [self tileScrollerViews];
  907.     
  908.     return self;
  909. }
  910.  
  911. /******************************************************************************
  912.     tileScrollerViews
  913.  
  914.     This method tiles the views in the scrollers. Views are added to the scroller in the order they are encountered in their respective list. If they don't leave at least an inch for the scroller if added, they are not added.
  915.     I had to pull this code out of the tile method because the scrollers need to be retiled occasionally after a reflectScroll (to see if the page buttons go away). Since tile indirectly causes a reflectScroll, it cannot be called from within reflectScroll. Returns self.
  916. ******************************************************************************/
  917. - tileScrollerViews
  918. {
  919.     NXRect aRect, bRect;
  920.     int i;
  921.     
  922.     // Reset scrollers to their origional sizes
  923.     aRect = bounds;
  924.     if([self borderType] == NX_LINE) NXInsetRect(&aRect, 1, 1);
  925.     else if([self borderType] == NX_BEZEL) NXInsetRect(&aRect, 2, 2);
  926.  
  927.     if (_sFlags.vScrollerRequired) {
  928.         NXDivideRect(&aRect, &bRect, NX_SCROLLERWIDTH, NX_XMIN);
  929.         [vScroller setFrame:&bRect];
  930.         NXDivideRect(&aRect, &bRect, 1.0, 0);
  931.     }
  932.     if (_sFlags.hScrollerRequired) {
  933.         NXDivideRect(&aRect, &bRect, NX_SCROLLERWIDTH, NX_YMAX);
  934.         [hScroller setFrame:&bRect];
  935.     }
  936.  
  937.     // Set frames for each horizontal scroller view in order
  938.     for(i=0; i<[horizScrollerViews count]; i++) {
  939.         id hView = [horizScrollerViews objectAt:i];
  940.         NXRect horizRect = [hScroller frame];
  941.         NXRect viewRect  = [hView frame];
  942.  
  943.         // This metric says that if there would be less than an inch of 
  944.         //    scroller, then don't add this view.
  945.         if((NX_WIDTH(&horizRect) - NX_WIDTH(&viewRect)) > 72) {
  946.  
  947.             // Stick pageLeftRightButtons on the left (all others on the right)
  948.             if(hView == [self pageLeftRightButtons]) {
  949.             
  950.                 // If there is no need for page left & right buttons move them
  951.                 if(![self needPageLeftRightButtons])
  952.                     { NX_X(&viewRect)=-1000; NX_Y(&viewRect)=-1000; }
  953.                 
  954.                 // Otherwise, calculate their rect from the left
  955.                 else 
  956.                     NXDivideRect(&horizRect,&viewRect,
  957.                         NX_WIDTH(&viewRect),NX_XMIN);
  958.             }
  959.  
  960.             // Stick all normal horiz scroller views to the right
  961.             else
  962.                 NXDivideRect(&horizRect,&viewRect,NX_WIDTH(&viewRect),NX_XMAX);
  963.  
  964.             // Set the scroller frame
  965.             [hScroller setFrame:&horizRect];
  966.  
  967.             // Adjust the new scroller view frame to fit in scroller and set
  968.             NX_Y(&viewRect)++; NX_HEIGHT(&viewRect) = NX_HEIGHT(&viewRect) - 2;
  969.             [hView setFrame:&viewRect];
  970.         }
  971.         else [hView moveTo:-1000 :-1000];
  972.     }
  973.  
  974.     // Set frames for each vertical scroller view in order
  975.     for(i=0; i<[vertScrollerViews count]; i++) {
  976.         id vView = [vertScrollerViews objectAt:i];
  977.         NXRect vertRect = [vScroller frame];
  978.         NXRect viewRect = [vView frame];
  979.  
  980.         // This metric says that if there would be less than an inch of 
  981.         //    scroller, then don't add this view.
  982.         if((NX_HEIGHT(&vertRect) - NX_HEIGHT(&viewRect)) > 72) {
  983.  
  984.             // Stick pageLeftRightButtons on the left (all others on the right)
  985.             if(vView == [self pageUpDownButtons]) {
  986.             
  987.                 // If there is no need for page up & down buttons move them
  988.                 if(![self needPageUpDownButtons])
  989.                     { NX_X(&viewRect)=-1000; NX_Y(&viewRect)=-1000; }
  990.                     
  991.                 // Otherwise, calculate their rect from the bottom
  992.                 else NXDivideRect(&vertRect, &viewRect, 
  993.                     NX_HEIGHT(&viewRect), NX_YMAX);
  994.             }
  995.  
  996.             // Stick all normal horiz scroller views at the top
  997.             else 
  998.                NXDivideRect(&vertRect,&viewRect, NX_HEIGHT(&viewRect),NX_YMIN);
  999.  
  1000.             // Set the scroller frame
  1001.             [vScroller setFrame:&vertRect];
  1002.             
  1003.             // Adjust the new scroller view frame to fit in scroller and set
  1004.             NX_X(&viewRect)++; NX_WIDTH(&viewRect) = NX_WIDTH(&vertRect) - 2;
  1005.             [vView setFrame:&viewRect];
  1006.         }
  1007.         else [vView moveTo:-1000 :-1000];
  1008.     }
  1009.     return self;
  1010. }
  1011.  
  1012. // Overridden to handle "Size to Fit" zoom setting.
  1013. - sizeTo:(NXCoord)width :(NXCoord)height
  1014. {
  1015.     // Let super do its sizing
  1016.     [super sizeTo:width :height];
  1017.  
  1018.     // If zoomButton is visible and set to "Fit" (tag 6) do appropriate zoomTo
  1019.     if([self zoomButtonVisible] && 
  1020.         ([[[[self zoomButton] target] itemList] selectedTag] == 6)) {
  1021.         NXRect docViewBounds, contentFrame;
  1022.         [[self docView] getBounds:&docViewBounds];
  1023.         [contentView getFrame:&contentFrame];
  1024.  
  1025.         // Zoom so that docView's bounds == content's frame
  1026.         [self zoomTo:NX_WIDTH(&contentFrame)/(NX_WIDTH(&docViewBounds)+1)
  1027.             :NX_HEIGHT(&contentFrame)/(NX_HEIGHT(&docViewBounds)+1)];
  1028.     }
  1029.  
  1030.     return self;
  1031. }
  1032.  
  1033. // Overridden to synchronize all views after addition.
  1034. - setDocView:view
  1035. {
  1036.     NXPoint point = [contentView boundsOrigin];
  1037.     id oldDocView = [super setDocView:view];
  1038.     [self scrollClip:contentView to:&point];
  1039.     return oldDocView;    
  1040. }
  1041.  
  1042. - write:(NXTypedStream *)stream
  1043. {
  1044.     [super write:stream];
  1045.     
  1046.     NXWriteTypes(stream, "@@c@@c#{ff}@@@@@@c@c@c@@",
  1047.         &topView,
  1048.         &topClip,
  1049.         &topViewVisible,
  1050.         &leftView,
  1051.         &leftClip,
  1052.         &leftViewVisible,
  1053.         &rulerClass,
  1054.         &rulerSize,
  1055.         &syncViews,
  1056.         &horizSyncViews,
  1057.         &vertSyncViews,
  1058.         &horizScrollerViews,
  1059.         &vertScrollerViews,
  1060.         &pageUpDownButtons,
  1061.         &pageUpDownButtonsVisible,
  1062.         &pageLeftRightButtons,
  1063.         &pageLeftRightButtonsVisible,
  1064.         &zoomButton,
  1065.         &zoomButtonVisible,
  1066.         &zoomPanel,
  1067.         &zoomText);
  1068.     
  1069.     return self;
  1070. }
  1071.  
  1072. - read:(NXTypedStream *)stream
  1073. {
  1074.     [super read:stream];
  1075.     
  1076.     NXReadTypes(stream, "@@c@@c#{ff}@@@@@@c@c@c@@",
  1077.         &topView,
  1078.         &topClip,
  1079.         &topViewVisible,
  1080.         &leftView,
  1081.         &leftClip,
  1082.         &leftViewVisible,
  1083.         &rulerClass,
  1084.         &rulerSize,
  1085.         &syncViews,
  1086.         &horizSyncViews,
  1087.         &vertSyncViews,
  1088.         &horizScrollerViews,
  1089.         &vertScrollerViews,
  1090.         &pageUpDownButtons,
  1091.         &pageUpDownButtonsVisible,
  1092.         &pageLeftRightButtons,
  1093.         &pageLeftRightButtonsVisible,
  1094.         &zoomButton,
  1095.         &zoomButtonVisible,
  1096.         &zoomPanel,
  1097.         &zoomText);
  1098.     
  1099.     return self;
  1100. }
  1101.  
  1102. // Hack methods to allow each type of sync and scroller view to be added in IB.
  1103. - setSyncViews:object
  1104. { [self addSyncView:object at:[[self syncViews] count]];return self;}
  1105. - setHorizSyncViews:object
  1106. { [self addHorizSyncView:object at:[[self horizSyncViews] count]];return self;}
  1107. - setVertSyncViews:object
  1108. { [self addVertSyncView:object at:[[self vertSyncViews] count]]; return self; }
  1109. - setHorizScrollerViews:object
  1110. { [self addHorizScrollerView:object at:[[self horizScrollerViews] count]];
  1111.     return self; }
  1112. - setVertScrollerViews:object
  1113. { [self addVertScrollerView:object at:[[self vertScrollerViews] count]];
  1114.     return self; }
  1115.  
  1116. // Interface Builder support
  1117. - (const char *)getInspectorClassName { return "PAScrollViewDeluxeInspector"; }
  1118.  
  1119. @end
  1120.  
  1121. @implementation View(Convenience)
  1122. - (NXCoord)frameX { return NX_X(&frame); }
  1123. - (NXCoord)frameY { return NX_Y(&frame); }
  1124. - (NXPoint)frameOrigin { return frame.origin; }
  1125. - (NXCoord)frameWidth { return NX_WIDTH(&frame); }
  1126. - (NXCoord)frameHeight { return NX_HEIGHT(&frame); }
  1127. - (NXSize)frameSize { return frame.size; }
  1128. - (NXRect)frame { return frame; }
  1129.  
  1130. - (NXCoord)boundsX { return NX_X(&bounds); }
  1131. - (NXCoord)boundsY { return NX_Y(&bounds); }
  1132. - (NXPoint)boundsOrigin { return bounds.origin; }
  1133. - (NXCoord)boundsWidth { return NX_WIDTH(&bounds); }
  1134. - (NXCoord)boundsHeight { return NX_HEIGHT(&bounds); }
  1135. - (NXSize)boundsSize { return bounds.size; }
  1136. - (NXRect)bounds { return bounds; }
  1137. @end
  1138.  
  1139. @implementation Scroller(PerCent)
  1140. - (float)perCent { return perCent; }
  1141. @end
  1142.